package bean.juego;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import logic.Buscador;
import bean.Equipo;
import bean.Grupo;
import bean.Logro;
import bean.Muerte;
import bean.jugador.AccionesBandera;
import bean.jugador.AccionesBandera.Acciones;
import bean.jugador.Jugador;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import logic.Filtro;
import logic.cache.CacheCaller;
import logic.cache.CacheService;
import logic.serial.bean.AccionBanderaCapturaSerial;
import logic.serial.bean.AccionBanderaJuegoSerial;
import logic.serial.bean.JuegoCTFSerial;
import logic.serial.bean.MuerteSerial;
import util.Converter;
import util.MyCollectionUtils;
import util.MyUtils;
import util.Pair;

public class JuegoCTF extends Juego {

    private static final Logger logger = Logger.getLogger(JuegoCTF.class.getName());
    private static final Pattern patternFlagGot = Pattern.compile("(.*?)CTF: .*?: (.*?) got the (RED|BLUE) flag!");
    private static final Pattern patternFlagCapture = Pattern.compile("(.*?)CTF: .*?: (.*?) captured the (RED|BLUE) flag!");
    private static final Pattern patternFlagCarrierFragged = Pattern.compile("(.*?)CTF: .*?: (.*?) fragged (RED|BLUE)'s flag carrier!");
    private static final Pattern patternFlagReturned = Pattern.compile("(.*?)CTF: .*?: (.*?) returned the (RED|BLUE) flag!");
    private static final Pattern patternCaptureLimit = Pattern.compile("(.*?) Exit: Capturelimit hit.*");
    private static final Pattern patternTimeLimit = Pattern.compile("(.*?) Exit: Timelimit hit.*");
    private List<AccionBanderaJuego> accionesBandera;
    private List<AccionBanderaCaptura> capturas;
    private Map<Equipo, List<Jugador>> capturadoresEnJuego;

//    private void calcularEquipoPorMuerte() {
//        if (this.equipos.get(Equipo.BLUE).size() + this.equipos.get(Equipo.RED).size()
//                != this.mapaPuntosJugador.size()) {
//            List<Muerte> muertesInverso = new ArrayList<Muerte>(this.muertes);
//            Collections.reverse(muertesInverso);
//            for (Muerte muerte : muertesInverso) {
//                this.calcularEquipoPorMuerte(muerte);
//            }
//        }
//    }

    public JuegoCTF(Map<String, String> parametrosJuego) {
        super(parametrosJuego);
        this.accionesBandera = new ArrayList<AccionBanderaJuego>();
        this.capturas = new LinkedList<AccionBanderaCaptura>();
        this.capturadoresEnJuego = new EnumMap<Equipo, List<Jugador>>(Equipo.class);
    }

//    private void calcularEquipoPorMuerte(Muerte muerte) {
//        Equipo equipo = this.getEquipo(muerte.getVictima());
//        if (equipo != null) {
//            this.equipos.get(MyUtils.contrario(equipo)).add(muerte.getVictimario());
//        } else {
//            equipo = this.getEquipo(muerte.getVictimario());
//            if (equipo != null) {
//                this.equipos.get(MyUtils.contrario(equipo)).add(muerte.getVictima());
//            }
//        }
//    }

    public void nuevoAccionBandera(String njugador, AccionesBandera.Acciones accion, String tiempo, String linea) {
        Jugador jugador = Buscador.jugadorPorApodo(njugador);
        List<Jugador> actuales;
        int segundos = toSeconds(tiempo);
        Equipo equipo = null;
        for (Equipo iterEquipo : Equipo.values()) {
            if (linea.contains(iterEquipo.toString())) {
                switch (accion) {
                    case CAPTURA:
                        equipo = MyUtils.contrario(iterEquipo);
                        actuales = this.capturadoresEnJuego.get(equipo);
                        this.capturas.add(new AccionBanderaCaptura(actuales, segundos - this.tiempoInicio, equipo));
                        this.capturadoresEnJuego.put(equipo, new ArrayList<Jugador>());
                        break;
                    case ENCUENTRO:
                        equipo = MyUtils.contrario(iterEquipo);

                        if (!this.capturadoresEnJuego.containsKey(equipo)) {
                            this.capturadoresEnJuego.put(equipo, new ArrayList<Jugador>());
                        }
                        actuales = this.capturadoresEnJuego.get(equipo);
                        actuales.add(jugador);
                        break;
                    case MUERTEBANDERISTA:
                        equipo = MyUtils.contrario(iterEquipo);
                        break;
                    case RETORNO:
                        equipo = iterEquipo;
                        this.capturadoresEnJuego.put(MyUtils.contrario(iterEquipo), new ArrayList<Jugador>());
                        break;
                }
            }
        }

        if (equipo != null) {
            this.equipos.get(equipo).add(jugador);
            this.equipos.get(MyUtils.contrario(equipo)).remove(jugador);
        }

        this.accionesBandera.add(new AccionBanderaJuego(accion, jugador, segundos - this.tiempoInicio, equipo));
    }

    public List<AccionBanderaJuego> getAccionesBandera() {
        return this.accionesBandera;
    }

    public int getCantidadCapturas(final Equipo equipo) {

        CacheCaller<Integer> caller = new CacheCaller<Integer>() {

            @Override
            public String getKey() {
                return JuegoCTF.this.getClass().getName() + "_" + Buscador.getListaJuegos().indexOf(JuegoCTF.this) + ".getCantidadCapturas:" + equipo;
            }

            @Override
            public Integer callMethod() {
                int cant = 0;

                for (AccionBanderaJuego accionBandera : JuegoCTF.this.accionesBandera) {
                    if (accionBandera.getAccion() == Acciones.CAPTURA && accionBandera.getEquipo() == equipo) {
                        cant++;
                    }
                }

                return cant;
            }
        };

        return CacheService.getFromCache(caller);
    }

    public List<AccionBanderaCaptura> getCapturas() {
        return this.capturas;
    }

    public void setValido(boolean valido) {
        //this.calcularEquipoPorMuerte();

        this.valido = valido;
    }

    @Override
    public boolean procesarLinea(BufferedReader leer) throws IOException {

        String linea = leer.readLine();

        if (linea == null) {
            return true;
        }

        if (this.procesarLinea(linea)) {
            return this.terminado;
        }

        Matcher m;

        m = patternFlagGot.matcher(linea);
        if (m.matches()) {
            this.nuevoAccionBandera(m.group(2), AccionesBandera.Acciones.ENCUENTRO, m.group(1), linea);
            return false;
        }

        m = patternFlagCapture.matcher(linea);
        if (m.matches()) {
            this.nuevoAccionBandera(m.group(2), AccionesBandera.Acciones.CAPTURA, m.group(1), linea);
            return false;
        }

        m = patternFlagCarrierFragged.matcher(linea);
        if (m.matches()) {
            this.nuevoAccionBandera(m.group(2), AccionesBandera.Acciones.MUERTEBANDERISTA, m.group(1), linea);
            return false;
        }

        m = patternFlagReturned.matcher(linea);
        if (m.matches()) {
            this.nuevoAccionBandera(m.group(2), AccionesBandera.Acciones.RETORNO, m.group(1), linea);
            return false;
        }

        m = patternCaptureLimit.matcher(linea);
        if (m.matches()) {
            this.finalizar(m.group(1));
            this.setGanadores(leer.readLine());
            this.setValido(true);
            return false;
        }

        m = patternTimeLimit.matcher(linea);
        if (m.matches()) {
            this.finalizar(m.group(1));
            this.setGanadores(leer.readLine());
            this.setValido(true);
            return false;
        }

        //Logger.getLogger(this.getClass().getName()).log(Level.SEVERE, "Linea desconocida: {0}", linea);

        return this.terminado;
    }

    @Override
    protected void accionesFinDeJuego() {
        if (this.isValido()) {
            super.accionesFinDeJuego();
            this.cargarAccionesBandera();
        }
    }

    private void cargarAccionesBandera() {
        for (AccionBanderaJuego accionBandera : this.getAccionesBandera()) {
            Jugador victimario = accionBandera.getJugador();

            AccionesBandera awards = victimario.getAccionesBandera();

            switch (accionBandera.getAccion()) {
                case CAPTURA:
                    awards.sumarBanderasCapturadas();
                    break;
                case ENCUENTRO:
                    awards.sumarBanderasEncontradas();
                    break;
                case MUERTEBANDERISTA:
                    awards.sumarBanderistasMuertos();
                    break;
                case RETORNO:
                    awards.sumarBanderasRetornadas();
                    break;
            }
        }
    }

    public JuegoCTFSerial toSerial() {
        JuegoCTFSerial serial = new JuegoCTFSerial();

        serial.setEsValido(this.valido);
        serial.setTerminado(this.terminado);
        serial.setTiempoInicio(this.tiempoInicio);
        serial.setTiempoFin(this.tiempoFin);
        serial.setGanador(this.ganador);
        serial.setParametrosJuego(this.parametrosJuego);
        serial.setFechaJuego(this.fechaJuego);

        serial.setAccionesBandera(MyCollectionUtils.convertList(accionesBandera,
                Converter.converterAccionBanderaJuegoToSerial));

        serial.setCapturas(MyCollectionUtils.convertList(capturas,
                Converter.converterAccionBanderaCapturaToSerial));

        serial.setCapturadoresEnJuego(MyCollectionUtils.convertMapValues(capturadoresEnJuego,
                new Converter<List<String>, List<Jugador>>() {

                    @Override
                    public List<String> convert(List<Jugador> a) {
                        return MyCollectionUtils.convertList(a,
                                Converter.converterJugadorToString);
                    }
                }));

        serial.setMapaPuntosJugador(MyCollectionUtils.convertMapKeys(mapaPuntosJugador,
                Converter.converterJugadorToString));

        serial.setMapaPuntosJugadorTiempo(MyCollectionUtils.convertMapValues(mapaPuntosJugadorTiempo,
                new Converter<Map<String, Integer>, Map<Jugador, Integer>>() {

                    @Override
                    public Map<String, Integer> convert(Map<Jugador, Integer> a) {
                        return MyCollectionUtils.convertMapKeys(a, Converter.converterJugadorToString);
                    }
                }));

        serial.setEquipos(MyCollectionUtils.convertMapValues(equipos,
                new Converter<Set<String>, Set<Jugador>>() {

                    @Override
                    public Set<String> convert(Set<Jugador> a) {
                        return MyCollectionUtils.convertSet(a, Converter.converterJugadorToString);
                    }
                }));

        serial.setLogros(MyCollectionUtils.convertList(logros,
                new Converter<Pair<Logro, String>, Pair<Logro, Jugador>>() {

                    @Override
                    public Pair<Logro, String> convert(Pair<Logro, Jugador> a) {
                        return new Pair<Logro, String>(a.getFirst(), a.getSecond().getDatos().getNombre());
                    }
                }));

        serial.setMuertes(MyCollectionUtils.convertList(muertes,
                new Converter<MuerteSerial, Muerte>() {

                    @Override
                    public MuerteSerial convert(Muerte a) {
                        return MuerteSerial.fromBean(a);
                    }
                }));

        return serial;
    }

    public static JuegoCTF fromSerial(JuegoCTFSerial serial) {
        JuegoCTF juego = new JuegoCTF(serial.getParametrosJuego());

        juego.valido = serial.isValido();
        juego.terminado = serial.isTerminado();
        juego.tiempoInicio = serial.getTiempoInicio();
        juego.tiempoFin = serial.getTiempoFin();
        juego.ganador = serial.getGanador();
        //this.parametrosJuego;
        //this.fechaJuego;

        juego.accionesBandera = MyCollectionUtils.convertList(serial.getAccionesBandera(),
                new Converter<AccionBanderaJuego, AccionBanderaJuegoSerial>() {

                    @Override
                    public AccionBanderaJuego convert(AccionBanderaJuegoSerial a) {
                        return a.toBean();
                    }
                });

        juego.capturas = MyCollectionUtils.convertList(serial.getCapturas(),
                new Converter<AccionBanderaCaptura, AccionBanderaCapturaSerial>() {

                    @Override
                    public AccionBanderaCaptura convert(AccionBanderaCapturaSerial a) {
                        return a.toBean();
                    }
                });

        juego.capturadoresEnJuego = MyCollectionUtils.convertMapValues(serial.getCapturadoresEnJuego(),
                new Converter<List<Jugador>, List<String>>() {

                    @Override
                    public List<Jugador> convert(List<String> a) {
                        return MyCollectionUtils.convertList(a, Converter.converterStringToJugador);
                    }
                });

        juego.mapaPuntosJugador = MyCollectionUtils.convertMapKeys(serial.getMapaPuntosJugador(),
                Converter.converterStringToJugador);

        juego.mapaPuntosJugadorTiempo = MyCollectionUtils.convertMapValues(serial.getMapaPuntosJugadorTiempo(),
                new Converter<Map<Jugador, Integer>, Map<String, Integer>>() {

                    @Override
                    public Map<Jugador, Integer> convert(Map<String, Integer> a) {
                        return MyCollectionUtils.convertMapKeys(a, Converter.converterStringToJugador);
                    }
                });

        juego.equipos = MyCollectionUtils.convertMapValues(serial.getEquipos(),
                new Converter<Set<Jugador>, Set<String>>() {

                    @Override
                    public Set<Jugador> convert(Set<String> a) {
                        return MyCollectionUtils.convertSet(a, Converter.converterStringToJugador);
                    }
                });

        juego.logros = MyCollectionUtils.convertList(serial.getLogros(),
                new Converter<Pair<Logro, Jugador>, Pair<Logro, String>>() {

                    @Override
                    public Pair<Logro, Jugador> convert(Pair<Logro, String> a) {
                        return new Pair<Logro, Jugador>(a.getFirst(), Buscador.jugadorPorApodo(a.getSecond()));
                    }
                });

        juego.muertes = MyCollectionUtils.convertList(serial.getMuertes(),
                new Converter<Muerte, MuerteSerial>() {

                    @Override
                    public Muerte convert(MuerteSerial a) {
                        return a.toBean();
                    }
                });

        return juego;
    }

    public Map<Equipo, Double> getProbabilidadsVictoria() {
        CacheCaller<Map<Equipo, Double>> caller = new CacheCaller<Map<Equipo, Double>>() {

            @Override
            public String getKey() {
                return JuegoCTF.this.getClass().getName() + "_" + Buscador.getListaJuegos().indexOf(JuegoCTF.this) + ".getProbabilidadsVictoria";
            }

            @Override
            public Map<Equipo, Double> callMethod() {
                List<Juego> juegosAnteriores = getJuegosAnteriores();

                Map<Equipo, Double> probabilidades = new EnumMap<Equipo, Double>(Equipo.class);
                for (Equipo equipo : Equipo.values()) {
                    List<Jugador> jugadores = new ArrayList<Jugador>(equipos.get(equipo));
                    List<Grupo> grupos = Filtro.getGruposJugadores(jugadores, juegosAnteriores);

                    Map<Integer, Pair<Integer, Double>> mapaPonderacion = new HashMap<Integer, Pair<Integer, Double>>();

                    for (Grupo grupo : grupos) {
                        if (grupo.getCantidadJuegosJugados() > 5) {
                            int peso = grupo.getJugadores().size();

                            if (!mapaPonderacion.containsKey(peso)) {
                                mapaPonderacion.put(peso, new Pair<Integer, Double>(0, 0d));
                            }

                            Pair<Integer, Double> pair = mapaPonderacion.get(peso);
                            mapaPonderacion.put(peso, new Pair<Integer, Double>(pair.getFirst() + 1, pair.getSecond() + grupo.getEfectividad()));
                        }
                    }

                    int denominador = 0;
                    double total = 0;
                    for (Map.Entry<Integer, Pair<Integer, Double>> entry : mapaPonderacion.entrySet()) {
                        Integer peso = entry.getKey();
                        Pair<Integer, Double> pair = entry.getValue();
                        denominador += peso;
                        if (pair.getFirst() > 0) {
                            total += (((double) pair.getSecond() / pair.getFirst()) * peso);
                        }
                    }

                    probabilidades.put(equipo, total / denominador);
                }

                return Collections.unmodifiableMap(probabilidades);
            }
        };

        return CacheService.getFromCache(caller);
    }

    public double getProbabilidadVictoria(Equipo equipo) {
        try {
            Map<Equipo, Double> probabilidades = getProbabilidadsVictoria();
            double pEquipo = probabilidades.get(equipo);
            double pContrario = probabilidades.get(MyUtils.contrario(equipo));

            if (pEquipo + pContrario == 0) {
                return 0d;
            } else {
                return pEquipo / (pEquipo + pContrario);
            }
        } catch (Exception ex) {
            logger.log(Level.SEVERE, null, ex);
            return 0;
        }
    }

    private List<Juego> getJuegosAnteriores() {

        CacheCaller<List<Juego>> caller = new CacheCaller<List<Juego>>() {

            @Override
            public String getKey() {
                return JuegoCTF.this.getClass().getName() + "_" + Buscador.getListaJuegos().indexOf(JuegoCTF.this) + ".getJuegosAnteriores";
            }

            @Override
            public List<Juego> callMethod() {
                List<Juego> allJuegos = Buscador.getListaJuegos();
                List<Juego> juegosAnteriores = new ArrayList<Juego>(allJuegos.size());
                for (Juego juego : allJuegos) {
                    if (juego instanceof JuegoCTF && !JuegoCTF.this.equals((JuegoCTF) juego) && juego.getFechaJuego().compareTo(JuegoCTF.this.fechaJuego) < 0) {
                        juegosAnteriores.add(juego);
                    }
                }

                juegosAnteriores.remove(JuegoCTF.this);
                return Collections.unmodifiableList(juegosAnteriores);
            }
        };

        return CacheService.getFromCache(caller);
    }
}
